﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Org.BouncyCastle.Asn1;
using System.IO;
using Signers = Org.BouncyCastle.Crypto.Signers;
using Crypto = Org.BouncyCastle.Crypto;

namespace TheClerk.Forms.Templates.raw.Envelope
{
    #region internal Commitment
    /// <summary>
    /// Hash over Assertions
    /// </summary>
    /// <remarks>
    /// Commitment ::= SEQUENCE {
    ///     hashAlgorithm AlgorithmIdentifier,
    ///     hashValue OCTET STRING }
    /// </remarks>
    internal class Commitment
    {
        // This can be either an OID or a BouncyCastle digest name string
        internal string AlgorithmID { get; private set; }

        // Holds a reference to the stream
        // Note: it's not evaluated until serialization
        internal Stream streamToHash;

        // The digest implementation
        Org.BouncyCastle.Crypto.IDigest myDigest;

        internal Commitment(string algID,
            System.IO.Stream toHash)
        {
            if (algID == null)
                throw new ArgumentNullException("algID");
            if (toHash == null)
                streamToHash = new MemoryStream();
            this.AlgorithmID = algID;
            this.streamToHash = toHash;
            myDigest = Org.BouncyCastle.Security.DigestUtilities.GetDigest(algID);
            AlgorithmID = Org.BouncyCastle.Security.DigestUtilities.GetAlgorithmName(Org.BouncyCastle.Security.DigestUtilities.GetObjectIdentifier(algID));
        }

        public Commitment(Org.BouncyCastle.Asn1.DerObjectIdentifier algID,
            byte[] contentToHash)
        {
            if (algID == null)
                throw new ArgumentNullException("algID");
            System.Diagnostics.Debug.Assert(contentToHash != null);
            if (contentToHash == null)
                streamToHash = new MemoryStream();
            else
                streamToHash = new MemoryStream(contentToHash);
            myDigest = Org.BouncyCastle.Security.DigestUtilities.GetDigest(algID);
            AlgorithmID = Org.BouncyCastle.Security.DigestUtilities.GetAlgorithmName(Org.BouncyCastle.Security.DigestUtilities.GetObjectIdentifier(algID.ToString()));
        }

        public Commitment(Org.BouncyCastle.Asn1.DerObjectIdentifier algID,
            System.IO.Stream toHash)
        {
            if (algID == null)
                throw new ArgumentNullException("algID");
            System.Diagnostics.Debug.Assert(toHash != null);
            if (toHash == null)
                this.streamToHash = new MemoryStream();
            else
                this.streamToHash = toHash;
            myDigest = Org.BouncyCastle.Security.DigestUtilities.GetDigest(algID);
            AlgorithmID = Org.BouncyCastle.Security.DigestUtilities.GetAlgorithmName(Org.BouncyCastle.Security.DigestUtilities.GetObjectIdentifier(algID.ToString()));
        }

        internal System.IO.Stream Serialize()
        {
            // verify assumptions

            Debug.Assert(this.streamToHash != null);
            Debug.Assert(this.streamToHash.Length > 0);
            Debug.Assert(this.myDigest != null);

            // Don't forget to rewind the stream just before processing it
            Stream output = new MemoryStream();

            myDigest.Reset();

            byte[] hashtemp = new byte[streamToHash.Length];
            streamToHash.Read(hashtemp, 0, hashtemp.Length);
            myDigest.BlockUpdate(hashtemp, 0, hashtemp.Length);

            byte[] outbytes = new byte[myDigest.GetDigestSize()];
            myDigest.DoFinal(outbytes, 0);

            // Now that we've got the hash, we need to present ourselves as a Sequence

            var seqgen = new Org.BouncyCastle.Asn1.DerSequenceGenerator(output);
            seqgen.AddObject(Org.BouncyCastle.Security.DigestUtilities.GetObjectIdentifier(AlgorithmID));
            seqgen.AddObject(new Org.BouncyCastle.Asn1.DerOctetString(outbytes));
            seqgen.Close();
            output.Position = 0;
            return output;
        }

        internal byte[] AsByteArray()
        {
            Stream s = Serialize();
            byte[] buf = new byte[s.Length];
            s.Read(buf, 0, buf.Length);
            return buf;
        }

        internal Asn1Encodable ToAsn1Encodable()
        {
            return Asn1Object.FromStream(Serialize());
        }
    }
    #endregion
}
